import ThreeJsModule from './ThreeJsModule.js'
import * as THREE from 'three/build/three.module.js'

class ThreeJsUtil {
  /**
   * 类初始化
   */
  constructor () {
    this.three = {}
    this.methods = {}
    this.events = {}
    this.params = {}

    this.container = ''
    this.scene = ''
    this.camera = ''
    this.renderer = ''

    this.onEvents = {}

    this.clock = new THREE.Clock()
  }

  /**
   * threeJs初始化
   * @param data {*}
   * @param callback function (scene, camera, renderer)
   */
  async init (data, callback) {
    this.three = data.three
    this.methods = data.methods
    this.events = data.events
    this.params = data.params

    this.container = data.params.container
    if (data.params.containerId) this.container = document.getElementById(data.params.containerId)

    const width = this.container.offsetWidth, height = this.container.offsetHeight, asp = width / height

    this.scene = new THREE.Scene() // 初始化场景

    this.camera = new THREE.PerspectiveCamera(45, asp, 1, 25000) // 初始化相机

    await this.addMethods()
    this.addEvent()

    callback(this.scene, this.camera, this.renderer)
  }

  /**
   * 添加方法（需添加的方法在init函数的data.methods中添加）
   * @protected
   */
  async addMethods () {
    const methods = this.methods
    if (methods.renderer) await this.addRenderer()
    if (methods.event) await this.addEvent()
    if (methods.light) await this.addLight()
    if (methods.controls) await this.addControls()
    if (methods.sky) await this.addSky()
    if (methods.animation) await this.animate()
    if (methods.postprocessing) await this.addPostprocessing()
    if (methods.statement) await this.statement()
  }

  /**
   * 加载模型
   * @param params （url, type, animations, scene） {url: 'http://www.model.cn' or {mtl: 'http://www.model.mtl.cn', obj: 'http://www.model.obj.cn'}, type: 'gltf', animations: true, scene: null}
   * @param callback
   * @param onProgress function
   * @return {obj, animations, mixer, actions}
   */
  async addModel (params, callback, onProgress) {
    const that = this
    params = params || {}
    const url = params.url
    if (!url) throw '模型url不能为空'
    if (typeof (url) === 'string') var filename = url.split('/').pop()
    const type = params.type || filename.split('.').pop().toLowerCase()
    const name = params.name || ''
    const scene = params.scene ? params.scene : this.scene

    var obj, animations, mixer, actions
    var GLTFLoader, OBJLoader, MMDLoader

    switch (type) {
      case 'gltf':
      case 'GLTF':
      case 'glb':
      case 'GLB':
        GLTFLoader = await ThreeJsModule.MTLLoader
        new GLTFLoader()
          .load(url, function (gltf) {
            obj = gltf.scene
            animations = gltf.animations
            obj.name = name
            scene.add(obj)
            if (params.animations) {
              const res = that.addAnimation(obj, animations)
              mixer = res.mixer
              actions = res.actions
            }
            callback(obj, animations, mixer, actions)
          }, (progress) => {
            if (onProgress) onProgress(progress)
          })
        break
      case 'fbx':
      case 'FBX':
        const FBXLoader = await ThreeJsModule.FBXLoader
        new FBXLoader()
          .load(url, function (fbx) {
            obj = fbx
            animations = fbx.animations
            obj.name = name
            scene.add(obj)
            if (params.animations) {
              const res = that.addAnimation(obj, animations)
              mixer = res.mixer
              actions = res.actions
            }
            callback(obj, animations, mixer, actions)
          }, (progress) => {
            if (onProgress) onProgress(progress)
          })
        break
      case 'draco-gltf':
      case 'DRACO-GLTF':
        const DRACOLoader = await ThreeJsModule.DRACOLoader
        var dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath('static/draco/gltf/') // 设置解压库文件路径
        dracoLoader.preload()
        GLTFLoader = await ThreeJsModule.MTLLoader
        new GLTFLoader()
          .setDRACOLoader(dracoLoader)
          .load(url, function (gltf) {
            obj = gltf.scene
            animations = gltf.animations
            obj.name = name
            scene.add(obj)
            if (params.animations) {
              const res = that.addAnimation(obj, animations)
              mixer = res.mixer
              actions = res.actions
            }
            callback(obj, animations, mixer, actions)
          }, (progress) => {
            if (onProgress) onProgress(progress)
          })
        break
      case 'obj':
      case 'OBJ':
        OBJLoader = await ThreeJsModule.OBJLoader
        new OBJLoader()
          .load(url, function (object) {
            obj = object
            obj.name = name
            callback(obj, animations, mixer, actions)
          }, (progress) => {
            if (onProgress) onProgress(progress)
          })
        break
      case 'mtl-obj':
      case 'MTL-OBJ':
        const MTLLoader = await ThreeJsModule.MTLLoader
        OBJLoader = await ThreeJsModule.OBJLoader
        new MTLLoader()
          .load(url.mtl, function (materials) {
            materials.preload()
            new OBJLoader()
              .setMaterials(materials)
              .load(url.obj, function (object) {
                obj = object
                obj.name = name
                callback(obj, animations, mixer, actions)
              }, (progress) => {
                if (onProgress) onProgress(progress)
              })
          })
        break
      case 'MMD':
      case 'mmd':
        MMDLoader = await ThreeJsModule.MMDLoader
        new MMDLoader().load(url, function (mmd) {
          obj = mmd
          obj.name = name
          scene.add(obj)
          callback(obj)
        }, (progress) => {
          if (onProgress) onProgress(progress)
        })
        break
      case 'MMD-VMD':
      case 'mmd-vmd':
        MMDLoader = await ThreeJsModule.MMDLoader
        new MMDLoader().loadWithAnimation(url.mmd, url.vmd, function (mmd) {
          obj = mmd.mesh
          obj.name = name
          scene.add(obj)
          callback(obj, mmd.animation)
        }, (progress) => {
          if (onProgress) onProgress(progress)
        })
        break
      default:
        throw `不支持的模型格式：${type}`
    }
    // if (type === 'gltf' || type === 'glb') {
    //     // console.log('开始加载' + type + '格式模型')
    // } else if (type === 'fbx') {
    //     // console.log('开始加载' + type + '格式模型')
    // } else if (type === 'draco-gltf') {
    //     // console.log('开始加载' + type + '格式模型')
    // } else if (type === 'obj') {
    //     // console.log(`开始加载${type}格式模型`)
    // } else if (type === 'mtl-obj') {
    //     // console.log('开始加载' + type + '格式模型')
    // }
  }

  /**
   * 添加模型模型监听事件
   * @param obj object 需要监听的模型集合
   * @param type str 事件类型
   * @param exist function 事件触发时，监听到模型时的触发函数
   * @param key str 事件标识符
   * @param params {*}
   * @param lose function 事件触发时，未监听到模型时的触发函数
   */
  addOn (obj, type, exist, key, params, lose) {
    if (this.onEvents[type + '_' + key]) {
      throw `事件监听${type}_${key}已存在`
      return
    }
    const that = this
    obj = obj.children.length ? obj : { children: [obj] }
    params = params ? params : {}
    const dom = params.dom ? params.dom : this.container
    const recursive = params.recursive || false
    this.onEvents[type + '_' + key] = (event) => {
      const objList = that.getIntersectObject(event, obj, recursive).objectList
      if (objList.length > 0) {
        exist(objList)
      } else {
        lose ? lose() : null
      }
      // console.log(type + '监听事件未拾取到对象。此消息来自：' + key)
    }
    dom.addEventListener(type, this.onEvents[type + '_' + key])
  }

  /**
   * 移除监听事件
   * @param type str 事件类型
   * @param key str 事件标识符
   * @param params {*}
   */
  rmOn (type, key, params) {
    if (this.onEvents[type + '_' + key]) {
      params = params ? params : {}
      const dom = params.dom ? params.dom : this.container
      dom.removeEventListener(type, this.onEvents[type + '_' + key])
      this.onEvents[type + '_' + key] = null
    } else {
      throw `事件监听${type}_${key}不存在`
    }
  }

  /**
   * 添加动画
   * @param model
   * @param animations
   * @return {mixer, actions}
   */
  addAnimation (model, animations) {
    const mixer = new THREE.AnimationMixer(model) // 创建混合器

    var actions = {}

    for (var i = 0; i < animations.length; i++) {
      const clip = animations[i]
      actions[clip.name] = mixer.clipAction(clip) // 返回动画操作对象
    }
    return {
      mixer,
      actions
    }
  }

  /**
   * 添加监听事件
   * @protected
   */
  addEvent () {
    const events = this.events
    if (events.windowResize) this.onWindowResize()
  }

  /**
   * 添加后置处理
   * @param callback
   * @param params {*} {scene}
   * @return {EffectComposer}
   */
  async addPostprocessing (callback, params) {
    const that = this
    params = params || {}
    const scene = params.scene || this.scene
    const renderer = params.renderer || this.renderer
    const camera = params.camera || this.camera

    const EffectComposer = await ThreeJsModule.EffectComposer
    this.composer = new EffectComposer(renderer) // 效果组合对象

    const RenderPass = await ThreeJsModule.RenderPass
    var renderScene = new RenderPass(scene, camera) // 新建场景通道
    this.composer.addPass(renderScene)

    // 抗锯齿
    const ShaderPass = await ThreeJsModule.ShaderPass
    const FXAAShader = await ThreeJsModule.FXAAShader
    this.effectFXAA = new ShaderPass(FXAAShader) // 着色通道
    this.effectFXAA.uniforms['resolution'].value.set(1 / this.container.offsetWidth, 1 / this.container.offsetHeight)
    this.composer.addPass(this.effectFXAA)

    this.params.postprocessing.forEach((pass) => {
      if (pass === 'OutlinePass') {
        ThreeJsModule.OutlinePass.then(OutlinePass=>{
          that.outlinePass = new OutlinePass(new THREE.Vector2(that.container.offsetWidth, that.container.offsetHeight), scene, camera) // 边缘发光通道
          that.composer.addPass(that.outlinePass)

          that.outlinePass.edgeStrength = 5 // 0.01-10
          that.outlinePass.edgeGlow = 0.5 // 0-1
          that.outlinePass.edgeThickness = 1 // 1-4
          that.outlinePass.pulsePeriod = 2 // 0-5
          that.outlinePass.usePatternTexture = false
          that.outlinePass.visibleEdgeColor.set('#18f2f2')
          that.outlinePass.hiddenEdgeColor.set('#ff0404')
        })
      } else if (pass === 'UnrealBloomPass') {
        renderer.toneMapping = THREE.ReinhardToneMapping

        ThreeJsModule.UnrealBloomPass.then(UnrealBloomPass=>{
          that.unrealBloomPass = new UnrealBloomPass(new THREE.Vector2(that.container.offsetWidth, that.container.offsetHeight), 1.5, 0.4, 0.85) // 边缘发光通道
          that.composer.addPass(that.unrealBloomPass)

          that.unrealBloomPass.renderToScreen = true
          renderer.toneMappingExposure = 1
          that.unrealBloomPass.threshold = 0
          that.unrealBloomPass.strength = 1.5
          that.unrealBloomPass.radius = 0
        })
      }
    })

    if (callback) callback()

    return this.composer
  }

  /**
   * 添加边缘发光通道
   * @param callback
   * @param params
   * @returns {*}
   */
  async addOutlinePass (callback, params) {
    params = params || {}
    const scene = params.scene || this.scene
    const renderer = params.renderer || this.renderer
    const camera = params.camera || this.camera
    const render = params.render || false

    const EffectComposer = await ThreeJsModule.EffectComposer
    const composer = params.composer || new EffectComposer(renderer) // 效果组合对象

    if (render) {
      const RenderPass = await ThreeJsModule.RenderPass
      var renderScene = new RenderPass(scene, camera) // 新建场景通道
      composer.addPass(renderScene)
    }

    const OutlinePass = await ThreeJsModule.OutlinePass
    const outlinePass = new OutlinePass(new THREE.Vector2(this.container.offsetWidth, this.container.offsetHeight), scene, camera) // 边缘发光通道
    composer.addPass(outlinePass)

    // 抗锯齿
    // const ShaderPass = await ThreeJsModule.ShaderPass
    // const FXAAShader = await ThreeJsModule.FXAAShader
    // this.effectFXAA = new ShaderPass(FXAAShader) // 着色通道
    // this.effectFXAA.uniforms['resolution'].value.set(1 / this.container.offsetWidth, 1 / this.container.offsetHeight)
    // composer.addPass(this.effectFXAA)

    outlinePass.edgeStrength = 5 // 0.01-10
    outlinePass.edgeGlow = 0.5 // 0-1
    outlinePass.edgeThickness = 1 // 1-4
    outlinePass.pulsePeriod = 2 // 0-5
    outlinePass.usePatternTexture = false
    outlinePass.visibleEdgeColor.set('#18f2f2')
    outlinePass.hiddenEdgeColor.set('#ff0404')

    if (callback) callback()

    return {
      composer,
      outlinePass
    }
  }

  /**
   * 添加局部辉光通道
   * @param callback
   * @param params
   * @returns {*}
   */
  async addUnrealBloomPass (callback, params) {
    params = params || {}
    const scene = params.scene || this.scene
    const renderer = params.renderer || this.renderer
    const camera = params.camera || this.camera
    const render = params.render || false

    renderer.toneMapping = THREE.ReinhardToneMapping
    const composer = params.composer || new EffectComposer(renderer) // 效果组合对象

    if (render) {
      const RenderPass = await ThreeJsModule.RenderPass
      var renderScene = new RenderPass(scene, camera) // 新建场景通道
      composer.addPass(renderScene)
    }

    const UnrealBloomPass = await ThreeJsModule.UnrealBloomPass
    const unrealBloomPass = new UnrealBloomPass(new THREE.Vector2(this.container.offsetWidth, this.container.offsetHeight), 1.5, 0.4, 0.85) // 边缘发光通道
    composer.addPass(unrealBloomPass)

    unrealBloomPass.renderToScreen = false
    renderer.toneMappingExposure = 1
    unrealBloomPass.threshold = 0
    unrealBloomPass.strength = 1.5
    unrealBloomPass.radius = 0

    if (callback) callback()

    return {
      composer,
      unrealBloomPass
    }
  }

  /**
   * 添加天空
   */
  async addSky () {
    const that = this
    // Add Sky
    await ThreeJsModule.Sky.then(Sky=>{
      this.sky = new Sky()
    })
    this.sky.scale.setScalar(450000)
    this.scene.add(this.sky)

    // Add Sun Helper
    this.sunSphere = new THREE.Mesh(
      new THREE.SphereBufferGeometry(20000, 16, 8),
      new THREE.MeshBasicMaterial({ color: 0xffffff })
    )
    this.sunSphere.position.y = -700000
    this.sunSphere.visible = false
    this.scene.add(this.sunSphere)

    var distance = 400000

    var uniforms = that.sky.material.uniforms
    uniforms['turbidity'].value = 10
    uniforms['rayleigh'].value = 2
    uniforms['mieCoefficient'].value = 0.005
    uniforms['mieDirectionalG'].value = 0.8
    uniforms['luminance'] = { value: 1 }

    var theta = Math.PI * (0.49 - 0.5)
    var phi = 2 * Math.PI * (0.25 - 0.5)

    that.sunSphere.position.x = distance * Math.cos(phi)
    that.sunSphere.position.y = distance * Math.sin(phi) * Math.sin(theta)
    that.sunSphere.position.z = distance * Math.sin(phi) * Math.cos(theta)

    that.sunSphere.visible = false

    uniforms['sunPosition'].value.copy(that.sunSphere.position)
  }

  /**
   * 添加渲染器
   * @param callback
   */
  async addRenderer (callback) {
    const that = this
    for (const renderer of this.params.renderer) {
      if (renderer === 'WebGLRenderer') {
        that.renderer = new THREE.WebGLRenderer(that.params.rendererParams || null) // 初始化渲染器
        that.renderer.setSize(that.container.offsetWidth, that.container.offsetHeight)
        that.container.appendChild(that.renderer.domElement)
      } else if (renderer === 'CSS2DRenderer') {
        const CSS2DRenderer = await ThreeJsModule.CSS2DRenderer;
        that.CSS2DRenderer = new CSS2DRenderer()
        that.CSS2DRenderer.domElement.style.position = 'absolute'
        that.CSS2DRenderer.domElement.style.top = 0
        that.CSS2DRenderer.setSize(that.container.offsetWidth, that.container.offsetHeight)
        that.container.appendChild(that.CSS2DRenderer.domElement)
      } else if (renderer === 'CSS3DRenderer') {
        const CSS3DRenderer = await ThreeJsModule.CSS3DRenderer;
        that.CSS3DRenderer = new CSS3DRenderer()
        that.CSS3DRenderer.domElement.style.position = 'absolute'
        that.CSS3DRenderer.domElement.style.top = 0
        that.CSS3DRenderer.setSize(that.container.offsetWidth, that.container.offsetHeight)
        that.container.appendChild(that.CSS3DRenderer.domElement)
      }
    }
  }

  /**
   * 添加控制器（内部调用允许重写，外部调用允许回调）
   * @return {*}
   */
  async addControls (callback) {
    const that = this
    for (const controls of this.params.controls)
     {
      if (controls === 'OrbitControls') {
        const OrbitControls = await ThreeJsModule.OrbitControls
        const orbitControls = new OrbitControls(that.camera, that.container)
        that.orbitControls = orbitControls
        // 如果使用animate方法时，将此函数删除
        // this.controls.addEventListener('change', render)
        // 使动画循环使用时阻尼或自转 意思是否有惯性
        orbitControls.enableDamping = true
        // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
        // this.orbitControls.dampingFactor = 0.25
        // 是否可以缩放
        orbitControls.enableZoom = true
        // // 是否自动旋转
        orbitControls.autoRotate = true
        orbitControls.autoRotateSpeed = 1
        // 设置相机距离原点的最近距离
        orbitControls.minDistance = 1
        // 设置相机距离原点的最远距离
        // orbitControls.maxDistance = 2000
        // 是否开启右键拖拽
        // orbitControls.enablePan = true
      } else if (controls === 'FirstPersonControls') {
        const FirstPersonControls = await ThreeJsModule.FirstPersonControls

        const firstPersonControls = new FirstPersonControls(that.camera)
        that.firstPersonControls = firstPersonControls
        firstPersonControls.enabled = true
        firstPersonControls.lookSpeed = 0.02 // 鼠标移动查看的速度
        firstPersonControls.movementSpeed = 10 //相机移动速度
        firstPersonControls.noFly = false
        firstPersonControls.constrainVertical = true // 约束垂直
        firstPersonControls.verticalMin = 1.0
        firstPersonControls.verticalMax = 2.0
        firstPersonControls.lon = 0 // 进入初始视角x轴的角度
        firstPersonControls.lat = 0 // 初始视角进入后y轴的角度
      } else if (controls === 'PointerLockControls') {
        ThreeJsModule.PointerLockControls.then(PointerLockControls=> {
          const pointerLockControls = new PointerLockControls(that.camera, that.container)
          that.pointerLockControls = pointerLockControls
          this.scene.add(pointerLockControls.getObject())
        })
      } else if (controls === 'firstPerson') {
        ThreeJsModule.FirstPersonCameraControls.then(FirstPersonCameraControls=>{
          const firstPerson = new FirstPersonCameraControls(that.camera, that.container)
          that.firstPerson = firstPerson
        })
      } else if (controls === 'thirdPerson') {
        ThreeJsModule.ThirdPersonCameraControl.then(ThirdPersonCameraControl=>{
          const thirdPerson = new ThirdPersonCameraControl(that.camera, that.orbitControls)
          that.thirdPerson = thirdPerson
        })
      } else if (controls === 'firstPerson2') {
        ThreeJsModule.FirstPersonCameraControl.then(FirstPersonCameraControl=>{
          const firstPerson2 = new FirstPersonCameraControl(this.camera, this.container)
          that.firstPerson2 = firstPerson2
        })

      }
    }

    if (callback) callback()
  }

  /**
   * 添加灯光（内部调用允许重写，外部调用允许回调）
   * @param params (scene)
   * @param callback
   * @return
   */
  addLight (callback, params) {
    params = params || {}
    const scene = params.scene ? params.scene : this.scene

    const ambientLight = new THREE.AmbientLight('#fff', 1)
    scene.add(ambientLight)
    if (callback) callback()
    return ambientLight
  }

  /**
   * 窗口尺寸（内部调用允许重写，外部调用允许回调）
   * @param callback
   */
  onWindowResize (callback) {
    const that = this
    window.addEventListener('resize', (e) => {
      const width = that.container.offsetWidth, height = that.container.offsetHeight
      that.camera.aspect = width / height
      that.camera.updateProjectionMatrix()
      that.renderer.setSize(width, height)
      if (that.CSS2DRenderer) that.CSS2DRenderer.setSize(width, height)
      if (that.CSS3DRenderer) that.CSS3DRenderer.setSize(width, height)
      if (that.composer) {
        that.composer.setSize(width, height)
        that.effectFXAA.uniforms['resolution'].value.set(1 / width, 1 / height)
      }
      if (callback) callback()
    }, false)
  }

  /**
   * 动画（内部调用允许重写，外部调用允许回调）
   * @param callback
   */
  animate (callback) {
    const that = this
    requestAnimationFrame(() => {
      that.animate(callback)
    })
    that.render()
    if (that.methods.TWEEN) ThreeJsModule.TWEEN.then(TWEEN => {TWEEN.update()})

    if (that.methods.controls) {
      if (that.orbitControls) {
        if (that.orbitControls.enabled === true) {
          that.orbitControls.update()
        }
      }
      if (that.firstPersonControls) {
        if (that.firstPersonControls.enabled === true) {
          that.firstPersonControls.update(that.clock.getDelta())
        }
      }
      if (that.pointerLockControls) {
        if (that.pointerLockControls.enabled === true) {
        }
      }
      if (that.firstPerson) {
        if (that.firstPerson.enabled === true) {
          that.firstPerson.update()
        }
      }
      if (that.firstPerson2) {
        if (that.firstPerson2.enabled === true) {
          that.firstPerson2.update()
        }
      }
      if (that.thirdPerson) {
        if (that.thirdPerson.enabled === true) {
          that.thirdPerson.update()
        }
      }
    }

    if (callback) callback()
  }

  /**
   * 渲染（内部调用允许重写，外部调用允许回调）
   * @param callback
   */
  render (callback) {
    if (this.methods.renderer) {
      if (this.CSS2DRenderer) this.CSS2DRenderer.render(this.scene, this.camera)
      if (this.CSS3DRenderer) this.CSS3DRenderer.render(this.scene, this.camera)
      if (this.renderer) this.renderer.render(this.scene, this.camera)
    }

    if (this.composer) this.composer.render()
    if (callback) callback()
  }

  /**
   * 销毁
   * @param callback
   */
  destroy (callback) {
    // 停止渲染
    cancelAnimationFrame(this.animationFrameCount)
    // 销毁场景子元素
    this.scene.children.forEach((item) => {
      this.scene.remove(item)
      item = null
    })
    // 销毁场景
    this.scene = null

    // 销毁相机
    this.camera = null

    // 销毁渲染器
    if (this.methods.renderer) {
      if (this.renderer) {
        this.renderer.dispose()
        this.renderer = null
      }
      if (this.CSS2DRenderer) {
        this.CSS2DRenderer.dispose()
        this.CSS2DRenderer = null
      }
      if (this.CSS3DRenderer) {
        this.CSS3DRenderer.dispose()
        this.CSS3DRenderer = null
      }
    }

    // 销毁控制器
    if (this.methods.controls) {
      if (this.orbitControls) {
        this.orbitControls.dispose()
        this.orbitControls = null
      }
      if (this.firstPersonControls) {
        this.firstPersonControls.dispose()
        this.firstPersonControls = null
      }
      if (this.pointerLockControls) {
        this.pointerLockControls.dispose()
        this.pointerLockControls = null
      }
      if (this.firstPerson) {
        this.firstPerson.removeEvents()
        this.firstPerson = null
      }
      if (this.thirdPerson) {
        this.thirdPerson.removeEvents()
        this.thirdPerson = null
      }
    }

    // 销毁后期处理
    if (this.methods.postprocessing) {
      if (this.outlinePass) {
        this.outlinePass.dispose()
        this.outlinePass = null
      }
      if (this.unrealBloomPass) {
        this.unrealBloomPass.dispose()
        this.unrealBloomPass = null
      }
    }

    if (callback) callback()
  }

  /**
   * 获取模型层级
   * @param model 模型
   * @return {*}
   */
  getModelTier (model) {
    const that = this
    if (model.type === 'Group') {
      var children = []
      model.children.forEach((item) => {
        children.push(that.getModelTier(item))
      })
      return {
        name: model.name,
        type: model.type,
        ID: model.ID,
        children
      }
    } else if (model.type === 'Mesh') {
      return {
        name: model.name,
        type: model.type,
        ID: model.ID
      }
    }
  }

  /**
   * 向树中每一级添加父级元素
   * @param tree Json
   * @param type String list or dict
   * @return {*}
   */
  addParentToTree (tree, type) {
    const that = this
    type = type || 'dist'
    if (type === 'dict' && tree.children) {
      tree.children.forEach((newTree) => {
        newTree.parent = tree
        that.addParentToTree(newTree, 'dict')
      })
    } else if (type === 'list') {
      tree.forEach((newTree) => {
        that.addParentToTree(newTree, 'dict')
      })
    }

    return tree
  }

  /**
   * 创建精灵文字
   * @param text str
   * @param options (fontSize,backgroundColor,color) {fontSize:20, backgroundColor: '#fff', color: '#fff'}
   * @returns {*}
   */
  createSpriteText (text, options) {
    if (!options) options = {}
    options.fontSize = options.fontSize || 12 // 字体大小，默认12
    const name = options.name || ''
    const scene = options.scene || this.scene
    const scale = options.scale || [100, 100, 1]
    const position = options.position || [0, 0, 0]

    const average = this.container.offsetWidth > this.container.offsetHeight ? this.container.offsetHeight / 180 : this.container.offsetWidth / 360
    const canvas = document.createElement('canvas')
    canvas.width = text.length * (options.fontSize || 18) * average // 画布宽，根据内容长度和字体大小自动调整
    canvas.height = options.fontSize * average // 画布高，根据字体大小自动调整
    const ctx = canvas.getContext('2d')
    ctx.fillStyle = options.backgroundColor || 'transparent' // 画布背景色，默认为透明色
    ctx.fillRect(0, 0, canvas.width, canvas.height) // 绘制矩阵，params:矩形的左上角的坐标，矩形的大小
    ctx.font = canvas.height + 'px \'微软雅黑\'' // 字体
    ctx.textAlign = 'center' // 文字排列
    ctx.textBaseline = 'middle' // 文本基线
    ctx.fillStyle = options.color // 字体颜色
    ctx.fillText(text, canvas.width / 2, canvas.height / 2)
    const texture = new THREE.Texture(canvas)
    texture.needsUpdate = true
    const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: texture }))
    sprite.scale.set(options.fontSize / average * text.length, options.fontSize / average, 1) // 缩放比例
    sprite.name = name
    scene.add(sprite)
    sprite.scale.set(scale[0], scale[1], scale[2]) // 缩放比例
    sprite.position.set(position[0], position[1], position[2]) // 模型位置
    return sprite
  }

  /**
   * 创建精灵标签
   * @param title str 标题
   * @param content str 内容
   * @param options (fontSize,backgroundColor,color,scene,scale,position) {fontSize:20, width: 200, backgroundColor: '#fff', color: '#fff'}
   * @returns {*}
   */
  createSpriteLabel (title, content, options) {
    if (!options) options = {}
    options.fontSize = options.fontSize || 15 // 字体大小，默认15
    options.width = options.width || 200 // 画布宽度，默认200
    const name = options.name || ''
    const scene = options.scene || this.scene
    const scale = options.scale || [100, 100, 1]
    const position = options.position || [0, 0, 0]

    const average = 1.2
    var canvas = document.createElement('canvas')
    var context = canvas.getContext('2d')
    canvas.width = options.width
    var lineHeight = options.fontSize * average // 行高，根据字体大小自动调整
    context.font = lineHeight + 'px \'微软雅黑\''
    var result = this.breakLinesForCanvas(content, context,
      options.width)

    canvas.height = options.fontSize * 2 + (result.length + 1) * lineHeight
    context.fillStyle = options.backgroundColor || 'rgba(0, 127, 127, 0.7)' // 画布背景色，默认为透明色
    context.fillRect(0, 0, canvas.width, canvas.height) // 绘制矩阵，params:矩形的左上角的坐标，矩形的大小
    context.fillStyle = options.color || '#06f5ed' // 字体颜色
    context.font = options.fontSize + 3 + 'px \'微软雅黑\''
    context.textBaseline = 'middle' // 文本基线
    context.fillText(title, 10, lineHeight)
    context.font = options.fontSize + 'px \'微软雅黑\''
    result.forEach(function (line, index) {
      context.fillText(line, 20, lineHeight * (index + 1) + options.fontSize * 2)
    })

    const texture = new THREE.Texture(canvas)
    texture.needsUpdate = true
    const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: texture }))
    sprite.name = name
    scene.add(sprite)
    sprite.scale.set(scale[0], scale[1], scale[2]) // 缩放比例
    sprite.position.set(position[0], position[1], position[2]) // 模型位置
    return sprite
  }

  /**
   * 创建div精灵标签
   * @param title str 标题
   * @param content str 内容
   * @param callback function 回调函数
   * @param options {*} (scene, dom)
   */
  async createDivSpriteLabel (title, content, callback, options) {
    if (!options) options = {}
    const name = options.name || ''
    const scene = options.scene ? options.scene : this.scene
    const scale = options.scale || [100, 100, 1]
    const position = options.position || [0, 0, 0]

    const dom = options.dom ? options.dom : document.body
    const labelBox = document.createElement('div')
    labelBox.className = 'spriteLabel-box'
    dom.appendChild(labelBox)

    const titleBox = document.createElement('div')
    titleBox.className = 'spriteLabel-title-box'
    labelBox.appendChild(titleBox)

    const titleText = document.createElement('span')
    titleText.className = 'spriteLabel-title-text'
    titleText.textContent = title
    titleBox.appendChild(titleText)

    const contentBox = document.createElement('div')
    contentBox.className = 'spriteLabel-content-box'
    labelBox.appendChild(contentBox)

    const contentText = document.createElement('span')
    contentText.className = 'spriteLabel-content-text'
    contentText.textContent = content
    contentBox.appendChild(contentText)

    const html2canvas = await ThreeJsModule.html2canvas
    html2canvas(labelBox, {}).then((canvas) => {
      const texture = new THREE.Texture(canvas)
      texture.needsUpdate = true
      const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: texture }))
      sprite.name = name
      scene.add(sprite)
      sprite.scale.set(scale[0], scale[1], scale[2]) // 缩放比例
      sprite.position.set(position[0], position[1], position[2]) // 模型位置
      labelBox.parentNode.removeChild(labelBox)
      if (callback) callback(sprite)
    })
  }

  /**
   * 创建2D展示标签
   * @param title
   * @param content
   * @returns {CSS2DObject}
   */
  async createShow2DLabel (title, content) {
    const labelBox = document.createElement('div')
    labelBox.className = 'label2d-box'

    const titleBox = document.createElement('div')
    titleBox.className = 'label2d-title-box'
    labelBox.appendChild(titleBox)

    const titleText = document.createElement('span')
    titleText.className = 'label2d-title-text'
    titleText.textContent = title
    titleBox.appendChild(titleText)

    const contentBox = document.createElement('div')
    contentBox.className = 'label2d-content-box'
    labelBox.appendChild(contentBox)

    const contentText = document.createElement('span')
    contentText.className = 'label2d-content-text'
    contentText.textContent = content
    contentBox.appendChild(contentText)

    const CSS2DObject = await ThreeJsModule.CSS2DObject
    const label2d = new CSS2DObject(labelBox)
    this.scene.add(label2d)
    label2d.scale.set(0.1, 0.1, 0.1) // 我们可以通过控制box的大小来控制label3d的大小
    return label2d
  }

  /**
   * 创建3D展示标签
   * @param title
   * @param content
   * @return {any}
   */
  async createShow3DLabel (title, content) {
    const labelBox = document.createElement('div')
    labelBox.className = 'label3d-box'
    labelBox.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')'

    const titleBox = document.createElement('div')
    titleBox.className = 'label3d-title-box'
    labelBox.appendChild(titleBox)

    const titleText = document.createElement('span')
    titleText.className = 'label3d-title-text'
    titleText.textContent = title
    titleBox.appendChild(titleText)

    const contentBox = document.createElement('div')
    contentBox.className = 'label3d-content-box'
    labelBox.appendChild(contentBox)

    const contentText = document.createElement('span')
    contentText.className = 'label3d-content-text'
    contentText.textContent = content
    contentBox.appendChild(contentText)

    const CSS3DObject = await ThreeJsModule.CSS3DObject;
    const label3d = new CSS3DObject(labelBox)
    this.scene.add(label3d)
    label3d.scale.set(0.1, 0.1, 0.1) // 我们可以通过控制box的大小来控制label3d的大小
    return label3d
  }

  /**
   * 三维坐标转屏幕坐标
   * @param vectorOrObject
   * @returns {{x: number, y: number}}
   */
  vectorToScreen (vectorOrObject) {
    let o
    if (vectorOrObject instanceof THREE.Vector3) {
      o = vectorOrObject
    } else if (vectorOrObject instanceof THREE.Object3D) {
      o = new THREE.Vector3(vectorOrObject.position.x, vectorOrObject.position.y, vectorOrObject.position.z)
    } else {
      throw  'the arguments is a object of Vector3 or Object3D'
    }
    const standardVector = o.project(this.camera) // 世界坐标转标准设备坐标
      , a = this.renderer.getSize(new THREE.Vector2()).width / 2
      , b = this.renderer.getSize(new THREE.Vector2()).height / 2
      , x = Math.round(standardVector.x * a + a) // 标准设备坐标转屏幕坐标
      , y = Math.round(-standardVector.y * b + b) // 标准设备坐标转屏幕坐标
    return {
      x,
      y
    }
  }

  /**
   * 地理坐标转2d平面
   * @param radius
   * @param lng
   * @param lat
   * @return {{x: number, y: number}}
   */
  geographicToPlaneCoords (radius, lng, lat) {
    return {
      x: (lat / 180) * radius,
      y: (lng / 180) * radius
    }
  }

  /**
   * 地理坐标转三维坐标
   * @param radius
   * @param lng
   * @param lat
   */
  geographicToVector (radius, lng, lat) {
    new THREE.Vector3().setFromSpherical(new THREE.Spherical(radius, (90 - lat) * (Math.PI / 180), (90 + lng) * (Math.PI / 180)))
  }

  /**
   * 地理坐标转三维坐标添加了高度
   * @param radius
   * @param lng
   * @param lat
   * @param height
   * @return {{x: number, y: number, z: number}}
   */
  geographicToVectorPosition (radius, lng, lat, height) {
    const phi = (180 + lng) * (Math.PI / 180)
    const theta = (90 - lat) * (Math.PI / 180)
    return {
      x: -radius * Math.sin(theta) * Math.cos(phi),
      y: radius * Math.cos(theta) + (height || 0),
      z: radius * Math.sin(theta) * Math.sin(phi)
    }
  }

  /**
   * 获取与射线相交的对象数组
   * @param event
   * @param parent
   * @param recursive
   * @returns {*}
   */
  getIntersectObject (event, parent, recursive) {
    event.preventDefault()
    const mouse = new THREE.Vector2()
    const raycaster = new THREE.Raycaster()
    let objectList = []
    try {
      mouse.x = (((event.clientX || (event.touches[0] ? event.touches[0].pageX : event.changedTouches[0].pageX)) - this.container.getBoundingClientRect().left) / this.container.offsetWidth) * 2 - 1
      mouse.y = -(((event.clientY || (event.touches[0] ? event.touches[0].pageY : event.changedTouches[0].pageY)) - this.container.getBoundingClientRect().top) / this.container.offsetHeight) * 2 + 1
      raycaster.setFromCamera(mouse, this.camera)
      // intersectObjects(object,recursive)object — 用来检测和射线相交的物体。recursive — 如果为true，它还检查所有后代。否则只检查该对象本身。缺省值为false。
      objectList = raycaster.intersectObjects((parent || this.scene).children, recursive)
    } catch (e) {
      // 鼠标越界
    }
    return {
      raycaster: raycaster,
      objectList: objectList
    }
  }

  /**
   * 从当前位置飞到指定位置
   * @param option
   * @returns {string|*}
   */
  async flyTo (option) {
    const TWEEN = await ThreeJsModule.TWEEN
    const controls = this.orbitControls
    option.position = option.position || [] // 相机新的位置
    option.controls = option.controls || [] // 控制器新的中心点位置(围绕词典旋转等)
    option.duration = option.duration || 1000 // 飞行时间
    option.easing = option.easing || TWEEN.Easing.Linear.None
    TWEEN.removeAll()
    const curPosition = this.camera.position
    const controlsTar = controls.target
    const tween = new TWEEN.Tween({
      x1: curPosition.x, // 相机当前位置x
      y1: curPosition.y, // 相机当前位置y
      z1: curPosition.z, // 相机当前位置z
      x2: controlsTar.x, // 控制当前的中心点x
      y2: controlsTar.y, // 控制当前的中心点y
      z2: controlsTar.z // 控制当前的中心点z
    }).to({
      x1: option.position[0], // 新的相机位置x
      y1: option.position[1], // 新的相机位置y
      z1: option.position[2], // 新的相机位置z
      x2: option.controls[0], // 新的控制中心点位置x
      y2: option.controls[1], // 新的控制中心点位置x
      z2: option.controls[2] // 新的控制中心点位置x
    }, option.duration).easing(TWEEN.Easing.Linear.None) // TWEEN.Easing.Cubic.InOut //匀速
    tween.onUpdate(() => {
      controls.enabled = false
      this.camera.position.set(tween._object.x1, tween._object.y1, tween._object.z1)
      controls.target.set(tween._object.x2, tween._object.y2, tween._object.z2)
      controls.update()
      if (option.update instanceof Function) {
        option.update(tween)
      }
    })
    tween.onStart(() => {
      controls.enabled = false
      if (option.start instanceof Function) {
        option.start()
      }
    })
    tween.onComplete(() => {
      controls.enabled = true
      if (option.done instanceof Function) {
        option.done()
      }
    })
    tween.onStop(() => option.stop instanceof Function ? option.stop() : '')
    tween.start()
    TWEEN.add(tween)
    return tween
  }

  /**
   * 飞行到模型
   * @param obj object 模型对象
   * @param option ｛*｝
   */
  flyToObj (obj, option) {
    option = option || {}
    var flyToOption = option.flyToOption || {
      position: [100, 100, 100], // 相机新的位置
      controls: [0, 0, 0], // 控制器新的中心点位置
      duration: 3000, // 飞行时间
      start: () => {
      },
      update: () => {
      },
      done: () => {
      },
      stop: () => {
      }
    }
    const vector = option.vector || new THREE.Vector3(1, 0, 0)
    const radiusRatio = option.radiusRatio || 3
    const objCenter = this.getObjWorldPosition(obj)
    flyToOption['controls'] = [objCenter.x, objCenter.y, objCenter.z]
    var box = new THREE.Box3()
    const objRadius = box.expandByObject(obj).max.distanceTo(box.expandByObject(obj).min) / 2
    flyToOption['position'] = [vector.x * objRadius * radiusRatio + objCenter.x, vector.y * objRadius * radiusRatio + objCenter.y, vector.z * objRadius * radiusRatio + objCenter.z]

    this.flyTo(flyToOption)
  }

  /**
   * 获取模型对象世界坐标
   * @param obj {*} 模型对象
   * @param scene {*} 模型对象所在场景
   * @return {THREE.Vector3}
   */
  getObjWorldPosition (obj, scene) {
    scene = scene || this.scene

    scene.updateMatrixWorld(true)
    var worldPosition = new THREE.Vector3()
    obj.getWorldPosition(worldPosition)
    return worldPosition
  }

  /**
   * 获取两模型对象间距离
   * @param obj1 {*} 模型对象
   * @param obj2 {*} 模型对象
   * @param params {*}
   * @return {*}
   */
  getObjDistance (obj1, obj2, params) {
    params = params || {}
    const scene = params.scene || this.scene

    return this.getObjWorldPosition(obj1, scene).distanceTo(this.getObjWorldPosition(obj2, scene))
  }

  /**
   * 添加漫游
   * @param obj
   */
  addRoam (obj) {
    const that = this
    // 漫游终点
    const destination = new THREE.Mesh(new THREE.CircleGeometry(2.5, 50), new THREE.MeshBasicMaterial({
      color: 'green',
      transparent: true,
      opacity: 0.2,
      side: THREE.DoubleSide
    }))
    destination.visible = false
    destination.rotateX(-0.5 * Math.PI)
    destination.name = 'destination'
    this.scene.add(destination)
    // 漫游终点图标范围
    const loader = new THREE.TextureLoader().setPath('static/model/img/')
    loader.load('destinationBorder.png', texture => {
      destination.material = new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        opacity: 0.9,
        side: THREE.DoubleSide,
        depthTest: false
      })
      const destinationBorder = new THREE.Mesh(new THREE.CircleGeometry(2.5, 50), destination.material)
      destination.add(destinationBorder)
    })
    this.destination = destination

    this.addOn(obj, 'mousemove',
      (objList) => {
        that.destination.position.copy(objList[0].point)
        that.destination.visible = true
      }, 'mousemove_destination',
      null,
      () => {
        that.destination.visible = false
      })

    that.addOn(destination, 'click',
      (objList) => {
        const x = that.destination.position.x
        const z = that.destination.position.z
        const y = that.camera.position.y
        const v = that.camera.position.clone().sub(that.orbitControls.target.clone()) // 差
        that.flyTo({
          position: [x, y, z],
          controls: [x - v.x, y - v.y, z - v.z],
          duration: 500,
          done: function () {
          }
        })
      }, 'click_destination')
  }

  /**
   * 判断是否为移动（手机平板）设备
   * @returns {boolean}
   */
  isMobile () {
    if (navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null) return true
    return false
  }

  /**
   * 判断是否连接网络
   * @return {boolean}
   */
  isNetwork () {
    if (navigator.onLine) return true
    return false
  }

  /**
   * 获取网络连接类型
   * @return {null|*}
   */
  getNetworkType () {
    if (this.isNetwork()) return navigator.connection.type
    return null
  }

  /**
   * 寻找切换断点
   * @protected
   * @param text String 文本内容
   * @param width 每行宽度
   * @param context object
   * @return {number}
   */
  findBreakPoint (text, width, context) {
    var min = 0
    var max = text.length - 1

    while (min <= max) {
      var middle = Math.floor((min + max) / 2)
      var middleWidth = context.measureText(text.substr(0, middle)).width
      var oneCharWiderThanMiddleWidth = context.measureText(text.substr(0, middle + 1)).width
      if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
        return middle
      }
      if (middleWidth < width) {
        min = middle + 1
      } else {
        max = middle - 1
      }
    }
    return -1
  }

  /**
   * 字符串按宽度拆分数组
   * @protected
   * @param text  String 文本内容
   * @param context object
   * @param width 每行宽度
   * @return {[]}
   */
  breakLinesForCanvas (text, context, width) {
    var result = []
    var breakPoint = 0

    while ((breakPoint = this.findBreakPoint(text, width, context)) !== -1) {
      result.push(text.substr(0, breakPoint))
      text = text.substr(breakPoint)
    }

    if (text) {
      result.push(text)
    }

    return result
  }

  /**
   * 声明
   */
  statement () {
    const styleTitle =`
            background:url() no-repeat left center;
            background-size:30px 40px;
            padding-left:40px;
            line-height:50px;
            font-size: 18px;
            font-weight:bold;
            color:#00D4FF
            `
    const styleContent = `
            background:none;
            line-height:30px;
            font-size: 18px;
            font-weight:bold;
            color:#00D4FF
            `
    const styleContent2 = `
            padding-left:40px;
            background:none;
            line-height:30px;
            font-size: 18px;
            font-weight:bold;
            color:#00D4FF
            `

    const styleContent3 = `
            padding-top:10px;
            background:none;
            line-height:30px;
            font-size: 15px;
            font-weight:bold;
            color:#00D4FF
            `

    const styleContent4 = `
            padding-left:60px;
            background:none;
            line-height:15px;
            font-size: 10px;
            font-weight:bold;
            color:#00D4FF
            `

    console.log(['%cThreeUtilUtil',
        '%c更新时间: 2024-05-21',
        '%c作者: 崔梦超',
        '%c版本号: v1.0.1',
        '%cgitee: https://gitee.com/tsuimengchao',
        '%c更新日志：',
        '%cv1.0.1：改为动态导入three库，若使用到才导入。'
      ].join('\n').toString(),
      styleTitle,
      styleContent,
      styleContent2,
      styleContent,
      styleContent2,
      styleContent3,
      styleContent4
    )
  }
}

export {ThreeJsUtil, ThreeJsModule, THREE}
